package net.gdface.utils.encrypt;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.InvalidPropertiesFormatException;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Properties;
import static net.gdface.utils.encrypt.AES128ECBNoPadding.wrapDecrypt;
import static net.gdface.utils.encrypt.AES128ECBNoPadding.wrapEncrypt;


/**
 * {@link Properties}的子类,
 * 基于{@link AES128ECBNoPadding}实现对{@link Properties}中指定的property值提供加密保护
 * @author guyadong
 *
 */
public class EncryptedProperties extends Properties {
	private static final long serialVersionUID = 2102942876991337031L;
	/**
	 * 密钥
	 */
	private final String password;
	/**
	 * 需要保护的key集合
	 */
	private final LinkedHashSet<String> protectedKeys = new LinkedHashSet<>();
	/**
	 * 是否保护所有key
	 */
	private final boolean protecteAll;
	/**
	 * 是否需要解密标志,用于控制{@link #get(Object)}对返回的值是否要解密,{@link #put(Object, Object)}对设置的值是否要加密,
	 */
	private boolean trans;
	/**
	 * 构造方法
	 * @param properties 原始的{@link Properties}实例,如果不为{@code null},则全部将其中的property加入当前对象
	 * @param password 密钥,用于加密解密,为{@code null}或空时不实现加密保护
	 * @param protectedKeys 指定需要加密保护的key集合,为{@code null}或空对所有key加密保护
	 */
	public EncryptedProperties(Properties properties, String password, Iterable<String> protectedKeys) {
		this.password = password;
		if(null != protectedKeys){
			for(String protectedKey:protectedKeys){
				if(null != protectedKey && protectedKey.length() > 0){
					this.protectedKeys.add(protectedKey);
				}
			}
			this.protecteAll = this.protectedKeys.isEmpty();
		}else{
			this.protecteAll = true;
		}
		if(null != properties){
			putAll(properties);
		}
		// 要放到putAll后面,因为构造函数中putAll不需要对值加密
		trans = true;
	}
	/**
	 * 构造方法
	 * @param password 密钥,用于加密解密,为{@code null}时不实现加密保护
	 * @param protectedKeys 指定需要加密保护的key集合,为{@code null}时不实现加密保护
	 */
	public EncryptedProperties(String password, Iterable<String> protectedKeys) {
		this(null, password, protectedKeys);
	}
	private EncryptedProperties(Builder builder) {
		this(builder.properties,builder.password,builder.protectedKeys);
	}

	@Override
	public String getProperty(String key) {
        Object oval = get(key);
        String sval = (oval instanceof String) ? (String)oval : null;
        return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
	}
	/** 
	 * 如果指定的key为受保护的key则对返回值解密再返回
	 * @see java.util.Hashtable#get(java.lang.Object)
	 */
	@Override
	public synchronized Object get(Object key) {
		Object value = super.get(key);
		if( trans && value instanceof String 
				&& password != null && password.length() > 0 
				&& (protecteAll || protectedKeys.contains(key))){
			try {
				return wrapDecrypt((String)value, password);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return value;
	}

	/**
	 * 如果指定的key为受保护的key则对要设置的值加密后再加入hashTable
	 * @see java.util.Hashtable#put(java.lang.Object, java.lang.Object)
	 */
	@Override
	public synchronized Object put(Object key, Object value) {
		if(trans && value instanceof String 
				&& password != null && password.length() > 0 
				&& (protecteAll || protectedKeys.contains(key))){
			try {
				value = wrapEncrypt((String)value, password);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return super.put(key, value);
	}
	@Override
	public void store(Writer writer, String comments) throws IOException {
		try {
			trans = false;
			super.store(writer, comments);			
		} finally {
			trans = true;
		}
	}

	@Override
	public void store(OutputStream out, String comments) throws IOException {
		try {
			trans = false;
			super.store(out, comments);
		} finally {
			trans = true;
		}
	}
	
	@Override
	public void storeToXML(OutputStream os, String comment) throws IOException {
		try {
			trans = false;
			super.storeToXML(os, comment);
		} finally {
			trans = true;
		}
	}

	@Override
	public void storeToXML(OutputStream os, String comment, String encoding) throws IOException {
		try {
			trans = false;
			super.storeToXML(os, comment, encoding);
		} finally {
			trans = true;
		}
	}

	@Override
	public synchronized void load(Reader reader) throws IOException {
		try {
			trans = false;
			super.load(reader);
		} finally {
			trans = true;
		}
	}

	@Override
	public synchronized void load(InputStream inStream) throws IOException {
		try {
			trans = false;
			super.load(inStream);
		} finally {
			trans = true;
		}
	}

	@Override
	public synchronized void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException {
		try {
			trans = false;
			super.loadFromXML(in);
		} finally {
			trans = true;
		}
	}
	@Override
	public String toString() {
        int max = size() - 1;
        if (max == -1)
            return "{}";

        StringBuilder sb = new StringBuilder();
        Iterator<Object> it = keySet().iterator();

        sb.append('{');
        for (int i = 0; ; i++) {
            Object key = it.next();
            Object value = get(key);
            sb.append(key   == this ? "(this Map)" : key.toString());
            sb.append('=');
            sb.append(value == this ? "(this Map)" : value.toString());

            if (i == max)
                return sb.append('}').toString();
            sb.append(", ");
        }
	}
	/**
	 * @return {@link Builder}实例
	 */
	public static Builder builder(){
		return new Builder();
	}
	
	/**
	 * 将{@link Properties}封装为{@link EncryptedProperties}实例<br>
	 * 如果{@code properties}已经是{@link EncryptedProperties}实例则直接返回
	 * @param properties
	 * @param password
	 * @param protectedKeys
	 * @return {@link EncryptedProperties}实例
	 * @see #EncryptedProperties(Properties, String, Iterable)
	 */
	public static EncryptedProperties wrap(Properties properties, String password, Iterable<String> protectedKeys){
		if(properties instanceof EncryptedProperties){
			return (EncryptedProperties)properties;
		}
		return new EncryptedProperties(properties,password,protectedKeys);
	}
	public static class Builder{
		private Properties properties = new Properties();
		private String password;
		private LinkedHashSet<String> protectedKeys = new LinkedHashSet<>();
		private Builder(){
			
		}
		
		/**
		 * @param properties 要设置的 properties
		 * @return 当前对象
		 */
		public Builder properties(Properties properties) {
			if(null != properties){
				this.properties = properties;
			}
			return this;
		}

		/**
		 * 设置密钥
		 * @param password 
		 * @return 当前对象
		 */
		public Builder password(String password) {
			this.password = password;
			return this;
		}
		/**
		 * 指定要保护的key
		 * @param protectedKeys 
		 * @return 当前对象
		 */
		public Builder protectedKeys(Iterable<String> protectedKeys) {
			if(null != protectedKeys){
				for(String key : protectedKeys){
					protectedKey(key);
				}
			}
			return this;
		}

		/**
		 * 指定要保护的key
		 * @param protectedKeys
		 * @return 当前对象
		 */
		public Builder protectedKeys(String... protectedKeys) {
			if(null != protectedKeys){
				for(String key : protectedKeys){
					protectedKey(key);
				}
			}
			return this;
		}
		/**
		 * 指定要保护的key
		 * @param protectedKey
		 * @return 当前对象
		 */
		public Builder protectedKey(String protectedKey) {
			if(null != protectedKey && protectedKey.length() > 0){
				this.protectedKeys.add(protectedKey);
			}
			return this;
		}
		/**
		 * 设置保护所有key
		 * @return 当前对象
		 */
		public Builder protectedAll(){
			protectedKeys.clear();
			return this;
		}
		/**
		 * 根据当前对象提供的参数创建{@link EncryptedProperties}实例
		 */
		public EncryptedProperties build(){
			return new EncryptedProperties(this);
		}
		public EncryptedProperties wrap(){
			return EncryptedProperties.wrap(properties, password, protectedKeys);
		}
	}

}
